package com.google.code.fluentjoiner;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import org.hamcrest.Matcher;

public abstract class IteratorSupplierBuilder<T> extends AbstractSupplierBuilder<Iterator<T>>
{
    public static <T> IteratorSupplierBuilder<T> fromValues(final T... values)
    {
        return fromIterable(Arrays.asList(values));
    }

    public static <T> IteratorSupplierBuilder<T> fromIterable(final Iterable<T> iterable)
    {
        return new IteratorSupplierBuilder<T>()
        {
            @Override
            protected Iterator<T> getSupplierResult()
            {
                return iterable.iterator();
            }
        };
    }

    public static <T> IteratorSupplierBuilder<T> fromIterator(final Iterator<T> iterator)
    {
        return new IteratorSupplierBuilder<T>()
        {
            @Override
            protected Iterator<T> getSupplierResult()
            {
                return iterator;
            }
        };
    }

    public final SupplierBuilder<ImmutableSet<T>> toImmutableSet()
    {
        final Supplier<Iterator<T>> supplier = getSupplier();
        return new AbstractSupplierBuilder<ImmutableSet<T>>()
        {
            @Override
            protected ImmutableSet<T> getSupplierResult()
            {
                return ImmutableSet.copyOf(supplier.get());
            }
        };
    }

    public final SupplierBuilder<ImmutableList<T>> toImmutableList()
    {
        final Supplier<Iterator<T>> supplier = getSupplier();
        return new AbstractSupplierBuilder<ImmutableList<T>>()
        {
            @Override
            protected ImmutableList<T> getSupplierResult()
            {
                return ImmutableList.copyOf(supplier.get());
            }
        };
    }

    public final SupplierBuilder<Set<T>> toSet()
    {
        final Supplier<Iterator<T>> supplier = getSupplier();
        return new AbstractSupplierBuilder<Set<T>>()
        {
            @Override
            protected Set<T> getSupplierResult()
            {
                return Sets.newHashSet(supplier.get());
            }
        };
    }

    public final <U> MapSupplierBuilder<U, T> indexToKeys(final Function<? super T, U> indexFunction)
    {
        final Supplier<Iterator<T>> supplier = getSupplier();
        return MapSupplierBuilder.indexToKeys(supplier, indexFunction);
    }

    public final <U> MapSupplierBuilder<T, U> indexToValues(final Function<? super T, U> indexFunction)
    {
        final Supplier<Iterator<T>> supplier = getSupplier();
        return MapSupplierBuilder.indexToValues(supplier, indexFunction);
    }

    public final SupplierBuilder<List<T>> toList()
    {
        final Supplier<Iterator<T>> supplier = getSupplier();
        return new AbstractSupplierBuilder<List<T>>()
        {
            @Override
            protected List<T> getSupplierResult()
            {
                return Lists.newArrayList(supplier.get());
            }
        };
    }

    /**
     * Calls the Supplier<T> instance at this point and insert the values into
     * an ArrayList internally.  If the Supplier is planned to be called at some
     * later time, but an underlying collection may change between the time of
     * creation and when it is called, then this method will copy the collection
     * at this particular point.
     * @return Instance of IteratorSupplierBuilder<T> with an internal copy of
     * the underlying collection.
     */
    public final IteratorSupplierBuilder<T> copy()
    {
        final ArrayList<T> listCopy = Lists.newArrayList(get());
        return IteratorSupplierBuilder.fromIterable(listCopy);
    }

    public final <U> IteratorSupplierBuilder<U> parallelElementTransform(final Function<T, U> function, final ExecutorService executorService)
    {
        final Supplier<Iterator<T>> supplier = getSupplier();
        return new IteratorSupplierBuilder<U>()
        {
            @Override
            protected Iterator<U> getSupplierResult()
            {
                try
                {
                    // Construct a list of the Callable instances to be submitted.
                    final ImmutableList<Callable<U>> callables = ImmutableList.copyOf(Iterators.transform(supplier.get(), new SerializableFunction<T, Callable<U>>()
                    {
                        @Override
                        public Callable<U> apply(final T from)
                        {
                            return new FunctionCallable(from, function);
                        }
                    }));

                    // Submit the Callable instances to the ExecutorService.
                    final List<Future<U>> futures = executorService.invokeAll(callables);

                    // Return and convert the result.
                    return Iterators.transform(futures.iterator(), new FutureToValueFunction<U>());
                }
                catch (final InterruptedException interruptedException)
                {
                    throw new RuntimeException("InterruptedException thrown.", interruptedException);
                }
            }
        };
    }

    public final <U> IteratorSupplierBuilder<U> elementTransform(final Function<T, U> function)
    {
        final Supplier<Iterator<T>> supplier = getSupplier();
        return new IteratorSupplierBuilder<U>()
        {
            @Override
            protected Iterator<U> getSupplierResult()
            {
                return Iterators.transform(supplier.get(), function);
            }
        };
    }

    public final IteratorSupplierBuilder<T> filter(final Matcher<? super T> matcher)
    {
        return filter(PredicateUtils.fromMatcher(matcher));
    }

    public final IteratorSupplierBuilder<T> filter(final Predicate<? super T> predicate)
    {
        final Supplier<Iterator<T>> supplier = getSupplier();
        return new IteratorSupplierBuilder<T>()
        {
            @Override
            protected Iterator<T> getSupplierResult()
            {
                return Iterators.filter(supplier.get(), predicate);
            }
        };
    }

    public final IteratorSupplierBuilder<T> add(final Iterator<? extends T> iterator)
    {
        final Supplier<Iterator<T>> supplier = getSupplier();
        return new IteratorSupplierBuilder<T>()
        {
            @Override
            protected Iterator<T> getSupplierResult()
            {
                return Iterators.concat(supplier.get(), iterator);
            }
        };
    }

    public final IteratorSupplierBuilder<T> reverse()
    {
        final Supplier<Iterator<T>> supplier = getSupplier();
        return new IteratorSupplierBuilder<T>()
        {
            @Override
            protected Iterator<T> getSupplierResult()
            {
                return Iterables.reverse(Lists.newArrayList(supplier.get())).iterator();
            }
        };
    }

    public final <U> SupplierBuilder<U> foldRight(final Fold<T, U> fold, final U initialValue)
    {
        return reverse().foldLeft(fold, initialValue);
    }

    public final <U> SupplierBuilder<U> foldLeft(final Fold<T, U> fold, final U initialValue)
    {
        final Supplier<Iterator<T>> supplier = getSupplier();
        return new AbstractSupplierBuilder<U>()
        {
            @Override
            protected U getSupplierResult()
            {
                final Iterator<T> iterator = supplier.get();
                U currentValue = initialValue;
                while(iterator.hasNext())
                {
                    currentValue = fold.apply(iterator.next(), currentValue);
                }
                return currentValue;
            }
        };
    }
}